package com.cpd.blog.common.handler;

import com.alibaba.fastjson.JSON;
import com.cpd.blog.common.constant.RedisKey;
import com.cpd.blog.common.constant.StatusCode;
import com.cpd.blog.common.utils.*;
import com.cpd.blog.dto.Result;
import com.cpd.blog.dto.User.UserVo;
import com.cpd.blog.service.UserService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 登录拦截器
 *
 * @author Copydown
 * @date 2021/8/17 15:19
 */
@Component
@Slf4j
@RequiredArgsConstructor
public class LoginInterceptor implements HandlerInterceptor {

    private final UserService userService;

    private final RedisUtil redisUtil;

    @Value("${web.token.prefix}")
    private String tokenPrefix;

    /**
     * 在执行 controller方法（Handler）之前执行
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        //1、判断请求接口的路径是否为 HandlerMethod（controller方法）
        if (!(handler instanceof HandlerMethod)) {
            //handler可能是访问资源的 RequestResourceHandler（classpath下的 static目录）
            return true;
        }

        String ip = IpUtils.getIpAddr(request);
        String token = request.getHeader(tokenPrefix);
        String method = request.getMethod();
        String requestUri = request.getRequestURI();

        //2、判断 token是否为空，为空则未登录
        if (StringUtils.isBlank(token)) {
            Result result = Result.fail(StatusCode.USER_NOT_LOGIN);
            writeResponse(response, result);
            return false;
        }

        //3、Redis 中token不存在则返回未登录
        UserVo userVo = userService.checkToken(token);
        if (userVo == null) {
            Result result = Result.fail(StatusCode.USER_NOT_LOGIN);
            writeResponse(response, result);
            return false;
        }

        //4、同一账号在别处登陆后则删除旧 token
        if (CheckUtil.isNotEquals(token, redisUtil.compareHashAndGet(RedisKey.IS_LOGIN, userVo.getId()))) {
            Result result = Result.fail(StatusCode.USER_NOT_LOGIN);
            writeResponse(response, result);
            Boolean delete = redisUtil.delete(RedisKey.TOKEN + token);
            if (delete) {
                LogUtil.log("删除过期 token 成功，userId：{}", userVo.getId());
            }
            return false;
        }

        //在执行 Controller方法前，塞一个ThreadLocal给他
        if (CurrentUser.isNull()) {
            CurrentUser.put(userVo);
        }
        LogUtil.log(">>>>> 已放行请求 {} ", ip, method, requestUri, String.valueOf(userVo.getId()));
        //4、认证成功  放行
        return true;
    }

    private void writeResponse(HttpServletResponse response, Result result) throws IOException {
        response.setContentType("application/json;charset=utf-8");
        response.setCharacterEncoding("UTF-8");
        response.getWriter().print(JSON.toJSONString(result));
    }

    /**
     * 在执行 controller方法（Handler）之后执行
     */
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        //若不删除ThreadLocal用完的信息，会有内存泄露的风险
        CurrentUser.remove();
    }
}
